package com.vcg.neo4j.dubbo.service;

import com.alibaba.dubbo.common.bytecode.ClassGenerator;
import com.alibaba.dubbo.common.compiler.support.JavassistCompiler;
import com.alibaba.dubbo.common.utils.ClassHelper;
import com.alibaba.dubbo.config.spring.ServiceBean;
import com.alibaba.dubbo.rpc.RpcException;
import javassist.ClassPool;
import javassist.LoaderClassPath;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ConfigurableApplicationContext;

import java.net.URL;
import java.net.URLClassLoader;

/**
 * Created by wuyu on 2016/8/5.
 */
public class DynamicExportServiceImpl implements DynamicExportService, DisposableBean, ApplicationContextAware {

    private JavassistCompiler javassistCompiler = new JavassistCompiler();

    private DynamicExportClassLoader dynamicExportClassLoader = new DynamicExportClassLoader();

    private ApplicationContext applicationContext;

    public <T> void export(String[] jars, String iFaceClazzName, String implClazzName) throws ClassNotFoundException {
        URLClassLoader urlClassLoader = getUrlClassLoader(jars);
        Class iFaceClazz = Class.forName(iFaceClazzName, true, urlClassLoader);
        Class implClazz = Class.forName(implClazzName, true, urlClassLoader);
        T t = (T) register(implClazz.getName(), implClazz);
        ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
        ClassPool classPool = ClassGenerator.getClassPool(contextClassLoader);
        LoaderClassPath loaderClassPath = new LoaderClassPath(urlClassLoader);
        classPool.insertClassPath(loaderClassPath);
        Thread.currentThread().setContextClassLoader(urlClassLoader);
        export(t, iFaceClazz);
    }



    public URLClassLoader getUrlClassLoader(String[] jarUrls) {
        URL[] urls = new URL[jarUrls.length];
        for (int i = 0; i < jarUrls.length; i++) {
            try {
                urls[i] = new URL(jarUrls[i]);
            } catch (Exception e) {
                throw new RpcException(e);
            }
        }
        return URLClassLoader.newInstance(urls, Thread.currentThread().getContextClassLoader());
    }


    public <T> T register(String id, Class<T> clazz) {
        ConfigurableApplicationContext application = getConfigurableApplicationContext();
        ConfigurableListableBeanFactory beanFactory = application.getBeanFactory();
        AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.genericBeanDefinition(clazz)
                .getBeanDefinition();
        DefaultListableBeanFactory defaultListableBeanFactory = (DefaultListableBeanFactory) beanFactory;
        defaultListableBeanFactory.registerBeanDefinition(id, beanDefinition);
        return (T) beanFactory.getBean(id);
    }


    private <T> void export(T t, Class iface) {
        ServiceBean<T> serviceBean = new ServiceBean<T>();
        if (iface == null) {
            Class<?>[] interfaces = t.getClass().getInterfaces();
            if (interfaces.length > 0) {
                serviceBean.setInterface(interfaces[0]);
            } else {
                throw new RpcException("the " + t.getClass().getName() + " Must implement the interface!");
            }
        } else {
            serviceBean.setInterface(iface);
        }
        serviceBean.setApplicationContext(this.applicationContext);
        serviceBean.setRef(t);
        serviceBean.setDelay(1);
        try {
            serviceBean.afterPropertiesSet();
        } catch (Exception e) {
            throw new RpcException(e);
        }
    }


    private Class<?> compile(String code) {
        return javassistCompiler.compile(code, ClassHelper.getCallerClassLoader(getClass()));
    }

    private ConfigurableApplicationContext getConfigurableApplicationContext() {
        return (ConfigurableApplicationContext) this.applicationContext;
    }


    public void destroy() throws Exception {
        applicationContext = null;
    }

    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }

}
